package org.reborn.engine.render.camera

import org.reborn.math._
import org.reborn.model.bound._

import javax.media.opengl._
import javax.media.opengl.fixedfunc.GLMatrixFunc
import glu.GLU

trait Camera {
  private val glu = new GLU()
  private var _recalcFrustum = true;
  private var _recalcProjectionMatrix = true;
  private var _nearPlaneDist = 0.01f
  private var _farPlaneDist = 100f
  private var _fov = 45.0f
  private var _widthToHeightRatio = 4.0f/3.0f
  
  private val _frustumPoints:Array[VectorF] = new Array[VectorF](5);
  private var _position:VectorF = VectorF(0f,0f,0f)
  
  /**
   * @return a matrix representing the rotation transformation for the camera
   */
  def rotation:MatrixF
  
  def position:VectorF = _position;
  def position_=(pos:VectorF):Unit = {_position = pos}

  def fieldOfView:Float = _fov;
  def fieldOfView_=(fov:Float):Unit = {_fov = fov; recalcProjectionMatAndFrustum();}

  def widthToHeightRatio:Float = _widthToHeightRatio;
  def widthToHeightRatio_=(ratio:Float):Unit = {_widthToHeightRatio = ratio; recalcProjectionMatAndFrustum();}
  
  def nearPlaneDist:Float = _nearPlaneDist;
  def nearPlaneDist_=(dist:Float):Unit = {_nearPlaneDist = dist; recalcProjectionMatAndFrustum();}

  def farPlaneDist:Float = _farPlaneDist;
  def farPlaneDist_=(dist:Float):Unit = {_farPlaneDist = dist; recalcProjectionMatAndFrustum();}
  
  /**
   * @return a matrix representing the rotation and translation transformation for the camera
   */
  def positionAndRotation():MatrixF = {
	MatrixF.TranslateMat(_position(0),_position(1),_position(2)) * rotation 
  }
	
  /**
   * @return a 3 dimensional vector representing the direction that the camera is facing
   */
  def direction():VectorF = {
	rotation * VectorF(0f,0f,-1f)
  }
  
  /**
   * Positions and orients the camera in the world.
   * Also sets the projection matrix if any of the projection
   * parameters have changed since the last frame.
   * @param gl
   */
  def setupCamera(gl:GL){
	val gl2 = gl.getGL2
	
	if(_recalcProjectionMatrix){
	  gl2.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
      gl2.glLoadIdentity();
	  glu.gluPerspective(_fov, _widthToHeightRatio, _nearPlaneDist, _farPlaneDist);
	  _recalcProjectionMatrix = false;
	}
	
	gl2.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
    gl2.glLoadIdentity();
    
    setCameraPositionAndOrientation(gl)
  }
  
  /**
   * @return an InfiniteFrustum bounding object with the camera's side planes
   */
  def frustum():InfiniteFrustum = {
	if(_recalcFrustum){
	  val ang = math.tan(_fov / 2).asInstanceOf[Float]
      val dyFar = ang * _farPlaneDist 
      val dxFar = dyFar * _widthToHeightRatio
	  	
      _frustumPoints(0) = VectorF(0,0,0)									//near point
      _frustumPoints(1) = VectorF(-dxFar,-dyFar,-_farPlaneDist).normalize	//left  - bottom - far
      _frustumPoints(2) = VectorF(dxFar,-dyFar,-_farPlaneDist).normalize	//right - bottom - far
      _frustumPoints(3) = VectorF(dxFar,dyFar,-_farPlaneDist).normalize		//right - top    - far
      _frustumPoints(4) = VectorF(-dxFar,dyFar,-_farPlaneDist).normalize	//left  - top    - far
    		  
	  _recalcFrustum = false
	}
	
	val mat = rotation
	
	val points = _frustumPoints.map((vec) => ((mat * vec) + _position).ofLength(3))
	  
    new InfiniteFrustum(points(0),points(4),points(3),points(1),points(2))
  }
  
  private def setCameraPositionAndOrientation(gl: GL): Unit = {
	val gl2 = gl.getGL2

	val mat = rotation
	gl2.glMultMatrixf(mat.as1DArray,0)
	gl2.glTranslatef(-_position(0),-_position(1),-_position(2))
  }
  
  private def recalcProjectionMatAndFrustum(){
	_recalcProjectionMatrix = true;
	_recalcFrustum = true;
  }
}